package com.android.segito;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CalendarContract;
import android.provider.CalendarContract.Events;
import android.util.Log;

import java.util.Collections;
import java.util.Date;
import java.util.Calendar;
import java.util.LinkedList;

/**
 * Enables various actions related to Android Calendar.
 * This calendar-access module assumes there is only a single calendar defined. It is possible to work with more,
 * but complicates things for now.
 * Events.HAS_EXTENDED_PROPERTIES is used as utility field for setting the Task's fluidity (1=true/0=false).
 * For some reason, the month here is the actual_month-1, e.g. 7 means August, 8 means September.
 * Based on: http://mobile.tutsplus.com/tutorials/android/android-essentials-adding-events-to-the-user%E2%80%99s-calendar/
 * @author Matan
 */
public class PACalendar
{

	private static final String EVENTS_ADDRESS = "content://com.android.calendar/events";

	private ContentResolver 	_cr;
	private Uri 				EVENTS_URI;

	/**
	 * Constructor.
	 * @param c application context.
	 */
	public PACalendar(Context c)
	{
		_cr = c.getContentResolver();
		EVENTS_URI = Uri.parse(EVENTS_ADDRESS);
	}

	/*
	private String getCalendarUriBase(Activity act) {

		String calendarUriBase = null;
		Uri calendars = Uri.parse("content://calendar/calendars");
		Cursor managedCursor = null;
		try {
			managedCursor = act.managedQuery(calendars, null, null, null, null);
		} catch (Exception e) {
		}
		if (managedCursor != null) {
			calendarUriBase = "content://calendar/";
		} else {
			calendars = Uri.parse("content://com.android.calendar/calendars");
			try {
				managedCursor = act.managedQuery(calendars, null, null, null, null);
			} catch (Exception e) {
			}
			if (managedCursor != null) {
				calendarUriBase = "content://com.android.calendar/";
			}
		}
		return calendarUriBase;
	}
	 */

	/**
	 * Add a Task to the calendar. Currently adding only these fields:
	 * Calendar id, title, startTime, endTime, location, description, and hasExtendedProperties (used for isFluid).
	 * CalendarContract: http://developer.android.com/reference/android/provider/CalendarContract.Events.html).
	 * @param t The given Task object to be added to the calendar.
	 */
	public void addTask(Task t)
	{
		Log.d("PACAlendar","addTask");

		ContentValues values = new ContentValues();

		values.put(Events.CALENDAR_ID, 1);
		values.put(Events.EVENT_TIMEZONE, "UTC");
		values.put(Events.TITLE, t.getTitle());
		values.put(Events.DTSTART, t.getStartTime().getTimeInMillis());
		values.put(Events.DTEND, t.getEndTime().getTimeInMillis());
		values.put(Events.EVENT_LOCATION, t.getLocation());

		if (t.isFluid())
		{
			values.put(Events.HAS_EXTENDED_PROPERTIES, 1);
			values.put(Events.DESCRIPTION, t.getDescription() + ";" +
						String.valueOf(t.isFluid()) + ";" +
						String.valueOf(((FluidTask)t).getDuration()) + ";" + 
						String.valueOf(((FluidTask)t).getDeadline().getTimeInMillis()));
			Log.d("PACalendar",";" +	String.valueOf(t.isFluid()) + ";duration;deadline");
		}
		else
		{
			values.put(Events.HAS_EXTENDED_PROPERTIES, 0);
			values.put(Events.DESCRIPTION,t.getDescription() + ";" + String.valueOf(t.isFluid()) + ";" );
		}

		_cr.insert(EVENTS_URI, values);
	}

	/**
	 * Deletes a task with the same title and description as t.
	 * @param t the task to delete.
	 */
	public void deleteTask(Task t)
	{
//		_cr.delete(	EVENTS_URI, "title=? and eventLocation=?",
//					new String[]{t.getTitle(), t.getLocation()} );
		_cr.delete(	EVENTS_URI, "title=?", new String[]{t.getTitle()} );
	}

	/**
	 * Deletes oldTask and adds newTask.
	 * @param oldTask The previous task, to be updated.
	 * @param newTask The new Task version.
	 */
	public void updateTask(Task oldTask, Task newTask)
	{
		deleteTask(oldTask);
		addTask(newTask);
	}

	/**
	 * Retrieves list of events in the calendar between 2 given Calendar's, returned as a LinkedList<Task>. 
	 * Based on http://lovingandroid.blogspot.co.il/2011/08/retrieving-events-from-calendar.html
	 * @param startC The starting point, a Calendar object indicating from when do we want to search the Android calendar.
	 * @param finishC Finishing point, until when to search in the Android calendar.
	 * @return A LinkedList<Task> that contains all the Tasks within the given time interval.
	 */
	public LinkedList<Task> getSchedule(Calendar start, Calendar end, boolean nightTasks)
	{
		LinkedList<Task> schedule = getScheduleWODefaultNightTasks(start, end);

		if (nightTasks)
			return fillSINightTasks(schedule, start, end);
		
		return schedule;
	}

	/**
	 * Checks if the time between start time and end time is free in the calendar.
	 * @param startTime The lower bound, from when to start looking.
	 * @param endTime The upper bound, until when to look.
	 * @return True iff the given interval is free of tasks, false otherwise.
	 */
	public boolean isFreeTime(Calendar startTime, Calendar endTime)
	{
		LinkedList<Task> tasks = getSchedule(startTime, endTime, false);
		return (tasks == null || tasks.isEmpty());
	}

	/**
	 * Get all the tasks from the calendar that match the given task's title.
	 * Easy to add more criteria to select by. Can be used to get more data 
	 * about a task when all we know is the title. Can be changed to search
	 * according to other criteria (start time etc.).
	 * @param t The given task, that has a matching title to the wanted Task from the calendar.
	 * @return A list of all the tasks that have the same title as the given Task.
	 */
	private boolean doesTaskExist(Task t)
	{
		Cursor cursor_event = _cr.query(	EVENTS_URI,	new String[]{"title"},
									"title ='" + t.getTitle() + "'", 
									null, null );

		if (cursor_event == null)
			return false;

		cursor_event.moveToFirst();
		String[] CalNames = new String[cursor_event.getCount()];

		if (CalNames.length == 0)
			return false;

		return true;
	}

	/**
	 * Given a List, for every Task in it, update its startTime and endTime (leaves everything else intact).
	 * Finds the task in the calendar according to Title (so DO NOT change the title when updating a Task).
	 * @param tasks The list of tasks that need updating.
	 * @return false iff the list is null or empty, true else.
	 */
	public boolean updateSchedule(LinkedList<Task> tasks)
	{
		if (tasks == null || tasks.isEmpty())
			return false;

		ContentValues values = null;
		boolean reys = false;
		int res = 0;
		
		for (Task t : tasks)
		{
			reys = doesTaskExist(t);
			if (reys == false)
			{
				Log.d("updateSchedule","Task does not exist");
				addTask(t);
			}
			else
			{
				values = new ContentValues();
				values.put(CalendarContract.Events.DTSTART, t.getStartTime().getTimeInMillis());
				values.put(CalendarContract.Events.DTEND, t.getEndTime().getTimeInMillis());

				res = _cr.update(	EVENTS_URI, values, "title=?",
									new String[]{t.getTitle()} );
				Log.d("PACalendar-UpdateSchedule", "Updated *" + res + "* rows.");
			}
		}

		return true;
	}

	/**
	 * Adds default fixed tasks at night, for every night between the first task in the given list, 
	 * and the last one. The tasks are between 22:00 and 08:00.
	 * Returns a modified LL of Tasks, with the defaults inside along with the normal tasks.
	 * @param startC The given list of Tasks from getSchedule (without the default night tasks).
	 * @param finishC The upper bound, until which we insert the night tasks.
	 * @return A list of Tasks, as given from getSchedule, in addition to night-tasks untill the given finishC.
	 */
	public LinkedList<Task> fillSINightTasks(LinkedList<Task> tasks, Calendar startC, Calendar finishC)
	{
		Task t = null;
		int i = 1;
		Calendar c = Calendar.getInstance();
		c.setTimeInMillis(startC.getTimeInMillis());

		if (finishC.get(Calendar.HOUR_OF_DAY) < 22)
		{
			return tasks;
		}
		
		while (c.before(finishC))
		{
			c = (Calendar) c.clone();

			// Set c1 to be 22:00 on the day of the first event.
			c.set(Calendar.HOUR_OF_DAY, 22);
			c.set(Calendar.MINUTE, 0);

			t = new FixedTask("Default Task #" + i++, null, null);
			t.setStart(c);
			c = (Calendar) c.clone();

			// Advance c1 to 08:00 (the next day).
			c.add(Calendar.HOUR_OF_DAY, 10);
			t.setEnd(c);
			tasks.add(t);
		}

		// Sort all the tasks with the new ones:
		Collections.sort(tasks, new TaskComparator());

		return tasks;
	}

	/**
	 * Get the closest Task within a week from the endTime of the given Task. If there
	 * is no such Task, returns null. If the arg is null, return the Task starting closest to now.
	 * @param t1 The Task about which want to know what is after it.
	 * @return If t1 isn't null, return the next Task that it's startTime is within a week of the endTime of t1. Otherwise,
	 * return the closest Task: ongoing right now, or the next one to start (within a week). In both cases, if there is no task
	 * within a week (from the given Task, or from right now) - return null.
	 */
	public Task getNextTask(Task t, boolean getDefaults)
	{
		Log.d("PACalendar","GetNextTask");
		LinkedList<Task> l = null;
		Calendar c = null, c1 = null;

		if (t == null)	// Start looking from now.
			c = Calendar.getInstance();
		else	// Start looking from when the given task's finished.
			c = t.getEndTime();

		// Create a calendar for 1 week ahead.
		c1 = (Calendar) c.clone();
		c1.add(Calendar.DAY_OF_YEAR, 7);

		if (!getDefaults) 
			l = getScheduleWODefaultNightTasks(c, c1);
		else
			l = getSchedule(c, c1, true);

		if (l == null || l.isEmpty())
			return null;
		else
			return l.getFirst();
	}

	/**
	 * Retrieves list of events in the calendar, returned as a ScheduleInfo object, but without the Default Night tasks, as returned in the 
	 * ordinary method getSchedule(Calendar,Calendar). Used only by the getNextTask() method.
	 * Based on http://lovingandroid.blogspot.co.il/2011/08/retrieving-events-from-calendar.html
	 * @param startC The starting point, a Calendar object indicating from when do we want to search the Android calendar.
	 * @param finishC Finishing point, until when to search in the Android calendar.
	 * @return A LinkedList<Task> that contains all the Tasks within the given time interval.
	 */
	public LinkedList<Task> getScheduleWODefaultNightTasks(Calendar startC, Calendar finishC)
	{
		if (finishC.before(startC))
			return null;

		long startDate = startC.getTimeInMillis();
		long finishDate = finishC.getTimeInMillis();
		Cursor cursor_event = null;
		boolean isTFluid = false;

		// Query for the desired events. The 3rd argument is a WHERE clause, possible to add parameters if needed.

		String[] projection = new String[]{"calendar_id", "title", "description", "dtstart", "dtend", "eventLocation", 
				Events.HAS_EXTENDED_PROPERTIES, Events.DURATION};

		String selection =
				//1. Tasks starting in the middle
				"(dtstart >= " + String.valueOf(startDate) + " AND dtstart < " + String.valueOf(finishDate) +
				//2. Tasks ending in the middle
				") OR (dtend > " + String.valueOf(startDate) + " AND dtend <= " + String.valueOf(finishDate) +
				//3. Tasks starting before and ending after.
				") OR (dtend > " + String.valueOf(finishDate) + " AND dtstart < " + String.valueOf(startDate) + ")";

		cursor_event = _cr.query(	EVENTS_URI, projection,
									selection, null, "dtstart ASC");

		if (cursor_event == null)
			return null;

		cursor_event.moveToFirst();

		String[] CalNames = new String[cursor_event.getCount()];
		int[] CalIds = new int[cursor_event.getCount()];
		LinkedList<Task> tasks = new LinkedList<Task>();
		Task t = null;

		for (int i = 0; i < CalNames.length; i++)
		{
			CalIds[i] = cursor_event.getInt(0);
			CalNames[i] = 	"Event" + cursor_event.getInt(0) + ": \nTitle: " + cursor_event.getString(1) + 
					"\nDescription: " + cursor_event.getString(2) + "\nStart Date: " + 
					new Date(cursor_event.getLong(3)) + "\nEnd Date : " + new Date(cursor_event.getLong(4)) +
					"\nLocation : " + cursor_event.getString(5) + "\n";

			int firstSeparIndex = cursor_event.getString(2).indexOf(";");
			int secondSeparIndex = cursor_event.getString(2).substring(firstSeparIndex + 1).indexOf(";") + firstSeparIndex+1;
			String boolAnswer = cursor_event.getString(2).substring(firstSeparIndex + 1, secondSeparIndex);
			//isTFluid = Boolean.getBoolean(boolAnswer);
			if (boolAnswer.equalsIgnoreCase("true"))
				isTFluid = true;
			else isTFluid = false;
			
			if (isTFluid)
			{
				// Description format is "description;isFluid;duration;deadline"
				int thirdSeparIndex = cursor_event.getString(2).substring(secondSeparIndex + 1).indexOf(";") + secondSeparIndex;
				long duration = Long.parseLong(cursor_event.getString(2).substring(secondSeparIndex + 1, thirdSeparIndex + 1));
				long deadline = Long.parseLong(cursor_event.getString(2).substring(thirdSeparIndex + 2));

				Calendar c = Calendar.getInstance();
				c.setTimeInMillis(deadline);

				t = new FluidTask(cursor_event.getString(1), duration, c);
			}
			else
			{
				t = new FixedTask(cursor_event.getString(1), null, null);
				t.setDescription(cursor_event.getString(2).substring(0, firstSeparIndex));
			}
			t.setLocation(cursor_event.getString(5));
			t.setDescription(cursor_event.getString(2).substring(0, firstSeparIndex));

			// Retrieve start and end times from the cursor.
			Calendar cStart = Calendar.getInstance();
			cStart.setTimeInMillis(cursor_event.getLong(3));
			Calendar cEnd = Calendar.getInstance();
			cEnd.setTimeInMillis(cursor_event.getLong(4));

			// Set the Task's start and end times.
			t.setStart(cStart);
			t.setEnd(cEnd);

			tasks.add(t);
			cursor_event.moveToNext();
		}

		cursor_event.close();
		return tasks;
	}

}